module AutoBase
  class Match
    attr_reader :matches
  
    # match command line prompt line by line
    def initialize
      @matchers = []
      @matches = {}
    end
  
    ## options
    #    :names => hash keys
    #    :at_most => has xxx lines at most
    #    :at_least => has xxx lines at least
    #    :no_newline => match line more than 1 times
    def has(line, opt = {})
      @matchers << [line, opt]
    end
  
    ## options
    #    :names => hash keys
    #    :no_newline => match line more than 1 times
    def has_one(line, opt = {})
      arg = opt.clone
      arg[:at_most] = nil
      arg[:at_least] = nil
      has(line, arg)
    end
  
    ## options
    #    :names => hash keys
    #    :at_least => has xxx lines at least
    #    :no_newline => match line more than 1 times
    def has_many(line, opt = {})
      arg = opt.clone
      arg[:at_most] = nil
      arg[:at_least] ||= 1
      has(line, arg)
    end
  
    def match(prompts)
      lines = prompts.split("\n")
      @matchers.each {|expect, opt|
        if opt[:at_most] == nil && opt[:at_least] == nil
          if m = line_match(lines, expect, opt)
            if opt[:no_newline]
              line = lines[0]
              index = line.index(m)
              lines[0] = line[index + m.size..-1]
            else
              lines.shift
            end
          else
            return false
          end
        else
          times = 0
          while true
            if m = line_match(lines, expect, opt)
              if opt[:no_newline]
                line = lines[0]
                index = line.index(m)
                lines[0] = line[index + m.size..-1]
              else
                lines.shift
              end
            
              times += 1
            else
              if opt[:at_least] && opt[:at_least] > times
                return false
              end
            
              if opt[:at_most] && opt[:at_most] < times
                return false
              end
           
              break
            end
          end
        end
      }
      return true
    end
  
    private
    def line_match(lines, expect, opt = {})
      if expect.is_a?(Regexp)
        line_regex(lines, expect, opt)
      else
        line_equal(lines, expect, opt)
      end
    end
  
    def line_regex(lines, expect, opt = {})
      line = lines[0]
      unless line
        puts lines.inspect
      end
      
      line_matcher = LineMatcher.new(expect, opt)
      if m = line_matcher.match(line)
        line_matcher.append_to(@matches)
        return m[0]
      else
        puts "<expect>#{expect},\n <but got>#{line}" if $DEBUG
        return false
      end
    end
  
    def line_equal(lines, expect, opt = {})
      line = lines[0]
      line_equaler = LineEqualer.new(expect, opt)
      if line_equaler.match(line)
        return line
      else
        puts "<expect>#{expect},\n <but got>#{line}" if $DEBUG
        return false
      end
    end
  
    class LineMatcher
      attr_reader :matches
      def initialize(matcher, opt = {})
        @names = opt[:names] || []
        @matcher = matcher
        @matches = nil
        if opt[:at_most] && opt[:at_most] > 1 or opt[:at_least]
          @has_many = true
        else
          @has_many = false
        end
      end
    
      def match(line)
        return false unless line
        m = line.match(@matcher)
        return false unless m
        @matches = {}
        @names.each_with_index {|name, index|
          if @has_many
            @matches[name] = [m[index + 1]]
          else
            @matches[name] = m[index + 1]
          end
        }
        return m
      end
    
      def append_to(matches)
        @names.each {|name|
          if matches[name].is_a?(Array)
            matches[name] += @matches[name]
          else
            matches[name] = @matches[name]
          end
        }
      end
    end
  
    class LineEqualer < LineMatcher
      def match(line)
        @matcher.strip == line.strip
      end
    end
  end
end